package org.libsdl.app;


import android.app.*;
import android.content.Context;
import android.view.*;
import android.os.*;
import android.util.Log;
import android.media.*;

/**
  * SDL Activity
  * Based on SDL sample, modified by ng
  *  - fixed oGL context problem
  *  - added mouse simulation support
  *  - added async & delayed mouse/keyboard events
  *  - added viewport support
  */
public class SDLActivity extends Activity
{

    // Main components
    private static SDLActivity mSingleton;
    private static SDLSurface mSurface;

    // Audio
    private static Thread mAudioThread;
    private static AudioTrack mAudioTrack;

    // Load the .so
    static
    {
        System.loadLibrary("SDL");
        //System.loadLibrary("SDL_image");
        //System.loadLibrary("SDL_mixer");
        //System.loadLibrary("SDL_ttf");
    }

    // Setup
    protected void onCreate(Bundle savedInstanceState)
    {
        //Log.v("SDL", "onCreate()");
        super.onCreate(savedInstanceState);

        // So we can call stuff from static callbacks
        mSingleton = this;

        // Set up the surface
        mSurface = createSDLSurface(getApplication());
        setContentView(mSurface);
        SurfaceHolder holder = mSurface.getHolder();
        holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
    }
    
    protected SDLSurface createSDLSurface(Context context)
    {
        return  new SDLSurface(context);
    }

    // Events
    protected void onPause()
    {
        //Log.v("SDL", "onPause()");
        super.onPause();
    }

    protected void onResume()
    {
        //Log.v("SDL", "onResume()");
        super.onResume();
    }

    // Messages from the SDLMain thread
    static final int COMMAND_CHANGE_TITLE = 1;
    static final int COMMAND_KEYB_EVENT = 10;
    static final int COMMAND_MOUSE_EVENT = 11;

    // Handler for the messages
    Handler commandHandler = new Handler()
    {
        public void handleMessage(Message msg)
        {
            switch(msg.what)
            {
                case COMMAND_CHANGE_TITLE:
                    setTitle((String)msg.obj);
                    break;
                case COMMAND_KEYB_EVENT:
                    sendKeyboardEvent(msg.arg1 != 0, msg.arg2);
                    break;
                case COMMAND_MOUSE_EVENT:
                    final MouseEvent me = (MouseEvent)msg.obj;
                    sendMouseEvent(me.action, me.button, me.x, me.y, 1.0f);
                    break;
            }
        }
    };

    // C functions we call
    static native final void nativeInit();
    static native final void nativeQuit();
    static native final void onNativeResize(int x, int y, int format);
    static native final void onNativeKeyDown(int keycode);
    static native final void onNativeKeyUp(int keycode);
    static native final void onNativeTouch(int action, float x, float y, float p);
    static native final void onNativeAccel(float x, float y, float z);
    static native final void nativeRunAudioThread();
    static native final void nativeSetOffsetAndScale(int x, int y, float scale);
    static native final void nativeSetRendererViewport(int x, int y, int w, int h);
    static native final void nativeGetRendererViewport(int [] v);
    
    // constant to use with onNativeTouch() (ng patch)
    public static final int ACTION_MASK         = 0x0000FFFF;
    public static final int MOUSE_BUTTON_LEFT   = 0x00000000;
    public static final int MOUSE_BUTTON_RIGHT  = 0x00010000;
    public static final int MOUSE_BUTTON_MIDDLE = 0x00020000;
    public static final int MOUSE_BUTTON_MASK   = 0x000F0000;
    
    /** Synchronous mouse simulation.
     * Coord are from SDL point of view. Use SDLSurface.sendMouseEvent() to take viewport into account.
     * @param action MoveEvent.ACTION_*
     * @param button SDLActivity.MOUSE_BUTTON_*
     * @param p optional pressure from 0f to 1f.
     * @see SDLSurface.sendMouseEvent()
     * @see SDLSurface.screenToViewportX()
     * @see SDLSurface.screenToViewportY()
     */
    public static final void sendMouseEvent(int action, int button, float x, float y, float p)
    {
        onNativeTouch(action | button, x, y, p);
    }
    
    /** Asynchronous mouse simulation
     * Coord are from SDL point of view.
     * @param action MoveEvent.ACTION_*
     * @param button SDLActivity.MOUSE_BUTTON_*
     * @param p optional pressure from 0f to 1f
     * @param delayMillis delay in ms
     * @see SDLSurface.sendMouseEvent()
     * @see SDLSurface.screenToViewportX()
     * @see SDLSurface.screenToViewportY()
     */
    public final void sendMouseAsyncEvent(int action, int button, float x, float y, float p, int delayMillis)
    {
        //TODO improve me with a circular buffer & a obtain method
        final MouseEvent e = new MouseEvent();
        e.x = x;
        e.y = y;
        e.action = action;
        e.button = button;
        final Message msg = commandHandler.obtainMessage(COMMAND_MOUSE_EVENT, e);
        commandHandler.sendMessageDelayed(msg, delayMillis);
    }
    
    /** Synchronous keyboard simulation
     * @param isKeyUp true for a keyUp event, false for a keyDown
     * @param keyCode keyCode given by android
     */
    public static final void sendKeyboardEvent(boolean isKeyUp, int keyCode)
    {
        if(isKeyUp)
            onNativeKeyUp(keyCode);
        else
            onNativeKeyDown(keyCode);
    }
    
    /** Asynchronous keyboard simulation
     * @param isKeyUp true for a keyUp event, false for a keyDown
     * @param keyCode keyCode given by android
     * @param delayMillis delay in ms
     */
    public final void sendKeyboardAsyncEvent(boolean isKeyUp, int keyCode, int delayMillis)
    {
        final Message msg = commandHandler.obtainMessage(COMMAND_KEYB_EVENT, isKeyUp ? 1 : 0, keyCode);
        commandHandler.sendMessageDelayed(msg, delayMillis);
    }


    // Java functions called from C

    public static boolean createGLContext(int majorVersion, int minorVersion)
    {
        return mSurface.initEGL(majorVersion, minorVersion);
    }

    public static void flipBuffers()
    {
        mSurface.flipEGL();
    }

    public static void setActivityTitle(String title)
    {
        // Called from SDLMain() thread and can't directly affect the view
        mSingleton.commandHandler.obtainMessage(COMMAND_CHANGE_TITLE, title).sendToTarget();
    }

    // Audio
    private static Object buf;

    public static Object audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames)
    {
        int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
        int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
        int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);

        Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + ((float)sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");

        // Let the user pick a larger buffer if they really want -- but ye
        // gods they probably shouldn't, the minimums are horrifyingly high
        // latency already
        desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);

        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
                channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);

        audioStartThread();

        Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + ((float)mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");

        if (is16Bit)
            buf = new short[desiredFrames * (isStereo ? 2 : 1)];
        else
            buf = new byte[desiredFrames * (isStereo ? 2 : 1)]; 
        return buf;
    }

    public static void audioStartThread()
    {
        mAudioThread = new Thread(new Runnable()
        {
            public void run()
            {
                mAudioTrack.play();
                nativeRunAudioThread();
            }
        });

        // I'd take REALTIME if I could get it!
        mAudioThread.setPriority(Thread.MAX_PRIORITY);
        mAudioThread.start();
    }

    public static void audioWriteShortBuffer(short[] buffer)
    {
        for (int i = 0; i < buffer.length; )
        {
            int result = mAudioTrack.write(buffer, i, buffer.length - i);
            if (result > 0)
            {
                i += result;
            }
            else if (result == 0)
            {
                try
                {
                    Thread.sleep(1);
                }
                catch(InterruptedException e)
                {
                    // Nom nom
                }
            }
            else
            {
                Log.w("SDL", "SDL audio: error return from write(short)");
                return;
            }
        }
    }

    public static void audioWriteByteBuffer(byte[] buffer)
    {
        for (int i = 0; i < buffer.length; )
        {
            int result = mAudioTrack.write(buffer, i, buffer.length - i);
            if (result > 0)
            {
                i += result;
            }
            else if(result == 0)
            {
                try
                {
                    Thread.sleep(1);
                }
                catch(InterruptedException e)
                {
                    // Nom nom
                }
            }
            else
            {
                Log.w("SDL", "SDL audio: error return from write(short)");
                return;
            }
        }
    }

    public static void audioQuit()
    {
        if (mAudioThread != null)
        {
            try
            {
                mAudioThread.join();
            }
            catch(Exception e)
            {
                Log.v("SDL", "Problem stopping audio thread: " + e);
            }
            mAudioThread = null;

            //Log.v("SDL", "Finished waiting for audio thread");
        }

        if (mAudioTrack != null)
        {
            mAudioTrack.stop();
            mAudioTrack = null;
        }
    }
    
    private static class MouseEvent
    {
        public float x, y;
        public int action, button;
    }
}



